{#- THE STORY OF A CHAPTER, as markup (#54, #55, #56). Rendered by the app page (chapter.html, `edit` true: the writing forms, photos served by /media) and inside the keepsake file (keepsake_inner.html, `edit` false, `imgs` = {media id: data: URI}). ONE partial, so the page and the file can never tell two different stories. Needs in context: s (story.build), edit (bool), imgs (dict or none). -#} {% import "_chart.html" as ch %} {% set c = s.chapter %}{% set sm = s.summary %} {% macro note(n, tok, edit) %} {% endmacro %}
{% if s.cover %}{% endif %}

📖 a chapter{{ " · shared" if s.shared }}

{{ c.title }}

{{ s.lo|local('%B %-d, %Y') }} — {{ s.last_day_ts|local('%B %-d, %Y') if not s.open else 'still being written' }} · {{ sm.days }} day{{ 's' if sm.days != 1 }}

{#- 🔴 one token for every edit form on the page: they are all "chapters.edit" -#} {% set tok = form_token('chapters.edit') if edit else '' %} {% if c.foreword or edit %}
{% if c.foreword %}

{{ c.foreword }}

{% endif %} {% if edit %}
✍️ {{ 'edit the foreword' if c.foreword else 'write a foreword — what this chapter was, in your words now' }}

{% if c.foreword %}empty it and save to remove it{% endif %}

{% endif %}
{% endif %} {#- 🎯 THE GOAL (M10 #60): the pup's want and the verdict core/quest.py reached. Absent from the shareable edition (story.build_shareable names no `goal`), so `s.goal` is simply missing there. -#} {% if s.goal or edit %}
{% if s.goal %}

🎯 the goal{% if s.goal.verdict %} · {{ {'met': 'reached', 'missed': 'not reached', 'open': 'still open', 'unmeasured': 'not measured'}[s.goal.verdict] }}{% endif %}

{% if s.goal.text %}

{{ s.goal.text }}

{% endif %}

{{ s.goal.label }}{% if s.goal.days %} · day {{ s.goal.day }} of {{ s.goal.days }}{% endif %}

{% endif %} {% if edit %}
🎯 {{ 'change the goal' if s.goal else 'set a goal — what this chapter is for, and how the log should measure it' }}

A habit goal is held to the habit's own weekly target — change that on Habits, not here. n is the days wanted, or for a habit the weeks on target wanted (0 = every full week). Empty the words and pick “words only” to remove the goal.

{% endif %}
{% endif %} {% if s.photos and edit %}
🖼 choose the cover photo
{% for p in s.photos %} {% endfor %}

{% endif %}
{{ sm.locked_seconds|dur }}locked, in all
{% if sm.longest_seconds %}
{{ sm.longest_seconds|dur }}longest stretch
{% endif %}
{{ sm.active_days }}of {{ sm.days }} days with something in them
{% if sm.reflections or sm.checkins %}
{{ sm.reflections }}reflection{{ 's' if sm.reflections != 1 }} · {{ sm.checkins }} check-in{{ 's' if sm.checkins != 1 }}
{% endif %} {% if sm.tasks_done %}
{{ sm.tasks_done }}task{{ 's' if sm.tasks_done != 1 }} done
{% endif %} {% if sm.photos %}
{{ sm.photos }}photo{{ 's' if sm.photos != 1 }}
{% endif %} {% if sm.badges %}
{{ sm.badges }}badge{{ 's' if sm.badges != 1 }} earned
{% endif %}
{% if sm.most_locked or sm.best_headspace %}

{% if sm.most_locked %}Most locked: {{ sm.most_locked.day|isodate('%A %b %-d') }} ({{ sm.most_locked.active_seconds|dur }}).{% endif %} {% if sm.best_headspace %}Best headspace: {{ sm.best_headspace.day|isodate('%A %b %-d') }} ({{ '%.1f'|format(sm.best_headspace.value) }}).{% endif %}

{% endif %}
{{ ch.panel("🔒 " ~ s.trends.hours.title, ch.bar(s.trends.hours, "the last bar is today, so far" if s.open else "")) }} {% if s.trends.scores %}{{ ch.panel("🧭 " ~ s.trends.scores.title, ch.line(s.trends.scores, "a gap is a day with no check-in")) }}{% endif %}
{% for it in s.days %} {% if it.quiet %}

· quiet for {{ it.n }} day{{ 's' if it.n != 1 }}{% if it.n > 1 %} — {{ it.first|isodate('%b %-d') }} to {{ it.last|isodate('%b %-d') }}{% endif %} ·

{% else %}

Day {{ it.n }} {{ it.day|isodate('%A, %B %-d') }}

{% if it.sentence %}

{{ it.sentence }}

{% endif %} {% if it.scores %}

{% for k, v in it.scores.items() %}{{ k }} {{ '%.0f'|format(v) if v == v|int else '%.1f'|format(v) }}{% endfor %}

{% endif %} {% if it.moments or it.badges %}
    {% for m in it.moments %}
  • {{ m.icon }} {{ m.text }} {{ m.ts|local('%H:%M') }}
  • {% endfor %} {% for b in it.badges %}
  • {{ b.icon or '🏅' }} Earned {{ b.title }}{% if b.detail %} — {{ b.detail }}{% endif %}
  • {% endfor %}
{% endif %} {% for e in it.entries %}
{{ {'reflection': '📓 reflection', 'checkin': '🧭 check-in', 'confession': '🙊 confession', 'catchup': '🗒 caught up'}[e.kind] }} · {{ e.ts|local('%H:%M') }} {% if e.prompt_text %}

{{ e.prompt_text }}

{% endif %} {% if e.body %}

{{ e.body }}

{% endif %}
{% endfor %} {% if it.photos %}
{% for p in it.photos %}{% if imgs %}{% if imgs[p.id] %}{{ p.caption or '' }}{% endif %}{% else %}{{ p.caption or '' }}{% endif %}{% endfor %}
{% endif %} {% for n in it.notes %}{{ note(n, tok, edit) }}{% endfor %} {% if edit %}
✍️ add a note about this day
{% if it.targets %}{% endif %}

{% endif %}
{% endif %} {% else %}

Nothing happened in this window yet.

{% endfor %}
{% if s.notes_undated %}

Notes

Written about days this chapter has not reached yet, or that arrived from another box.

{% for n in s.notes_undated %}

{{ n.day|isodate('%A, %B %-d') }}

{{ note(n, tok, edit) }}{% endfor %}
{% endif %} {% if edit %}
✍️ add a note about any day in this chapter — a quiet one too
{% include "_keepsake_form.html" %} {% endif %}